package com.youlu.campus.admin.auth.shiro.config;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.youlu.campus.admin.auth.shiro.credentials.AuthCredentialsMatcher;
import com.youlu.campus.admin.auth.shiro.credentials.AuthCredentialsService;
import com.youlu.campus.admin.auth.shiro.factory.AuthShiroFilterFactoryBean;
import com.youlu.campus.admin.auth.shiro.factory.StatelessDefaultSubjectFactory;
import com.youlu.campus.admin.auth.shiro.filter.AuthcAuthenticationFilter;
import com.youlu.campus.admin.auth.shiro.filter.AuthenticationFilter;
import com.youlu.campus.admin.auth.shiro.filter.LogoutAuthenticationFilter;
import com.youlu.campus.admin.auth.shiro.filter.UriPermissionsFilter;
import com.youlu.campus.admin.auth.shiro.permission.UriPermissionResolver;
import com.youlu.campus.admin.auth.shiro.realm.AuthRealm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;

import java.util.Map;

/**
 *
 * @author zhuhuaiqi
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(AuthProperties.class)
@ConditionalOnClass({ShiroFilterFactoryBean.class})
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)
@ConditionalOnProperty(value = "campus.auth.web", matchIfMissing = false)
public class ShiroAutoConfiguration {

    protected static final Logger logger = LoggerFactory.getLogger(ShiroAutoConfiguration.class);

    private AuthProperties authProperties;

    private int COOKIE_MAX_AGE = 60 * 60 * 24 * 2;

    public ShiroAutoConfiguration(AuthProperties authProperties) {
        logger.debug(":>>> ShiroAutoConfiguration authProperties:{}", JSON.toJSONString(authProperties));
        this.authProperties = authProperties;
    }

    @Bean
    public AuthCredentialsService getAuthCredentialsService() {
        AuthCredentialsService authCredentialsService = new AuthCredentialsService();
        authCredentialsService.setExpiresIn(authProperties.getAssessTokenExpiresIn()); //24 小时
        authCredentialsService.setIssuer(authProperties.getCookieDomain());
        return authCredentialsService;
    }

    @Bean(name = "customAuthorizingRealm")
    public AuthorizingRealm getCustomAuthorizingRealm() {
        AuthRealm realm = new AuthRealm();
        realm.setCachingEnabled(false);
        realm.setCredentialsMatcher(getCustomCredentialsMatcher());
        realm.setAuthCredentialsService(getAuthCredentialsService());
        return realm;
    }

    @Bean
    public CredentialsMatcher getCustomCredentialsMatcher() {
        AuthCredentialsMatcher credentialsMatcher = new AuthCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");
        credentialsMatcher.setHashIterations(2);
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }

    @Bean
    public DefaultWebSubjectFactory getSubjectFactory() {
        //Subject工厂
        StatelessDefaultSubjectFactory subjectFactory = new StatelessDefaultSubjectFactory();
        return subjectFactory;
    }

    @Bean
    public DefaultWebSessionManager getSessionManager() {
        //会话管理器
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setGlobalSessionTimeout(60);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionIdCookieEnabled(Boolean.TRUE);
        return sessionManager;
    }

    @Bean
    public PermissionResolver getUriPermissionResolver() {
        return new UriPermissionResolver();
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setRealm(getCustomAuthorizingRealm());
        //securityManager.setSessionManager(getSessionManager());
        securityManager.setSubjectFactory(getSubjectFactory());
        SessionStorageEvaluator sessionStorageEvaluator = ((DefaultSubjectDAO)
                securityManager.getSubjectDAO()).getSessionStorageEvaluator();
        ((DefaultSessionStorageEvaluator) sessionStorageEvaluator).setSessionStorageEnabled(false);

        ((ModularRealmAuthorizer) securityManager.getAuthorizer())
                .setPermissionResolver(getUriPermissionResolver());

        return securityManager;
    }

    @Bean
    public SimpleCookie getSessionIdCookie() {
        SimpleCookie cookie = new SimpleCookie("access_token");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(COOKIE_MAX_AGE);
        cookie.setDomain(authProperties.getCookieDomain());
        cookie.setPath(authProperties.getCookiePath());
        log.info(":>>> 从Cookie 获取access_token:{} ",JSON.toJSON(cookie));
        return cookie;
    }

    @Bean
    public SimpleCookie getUsernameCookie() {
        SimpleCookie cookie = new SimpleCookie("username");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(COOKIE_MAX_AGE);
        cookie.setDomain(authProperties.getCookieDomain());
        cookie.setPath(authProperties.getCookiePath());
        log.info(":>>> 从Cookie 获取username:{} ",JSON.toJSON(cookie));
        return cookie;
    }

    @Bean
    public SimpleCookie getUserIdCookie() {
        SimpleCookie cookie = new SimpleCookie("userId");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(COOKIE_MAX_AGE);
        cookie.setDomain(authProperties.getCookieDomain());
        cookie.setPath(authProperties.getCookiePath());
        log.info(":>>> 从Cookie 获取userId:{} ",JSON.toJSON(cookie));
        return cookie;
    }

    @Bean
    public SimpleCookie getComIdCookie() {
        SimpleCookie cookie = new SimpleCookie("tenantId");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(COOKIE_MAX_AGE);
        cookie.setDomain(authProperties.getCookieDomain());
        cookie.setPath(authProperties.getCookiePath());
        log.info(":>>> 从Cookie 获取tenantId:{} ",JSON.toJSON(cookie));
        return cookie;
    }

    @Bean
    public SimpleCookie getDomainCookie() {
        SimpleCookie cookie = new SimpleCookie("domain");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(COOKIE_MAX_AGE);
        cookie.setDomain(authProperties.getCookieDomain());
        cookie.setPath(authProperties.getCookiePath());
        log.info(":>>> 从Cookie 获取domain:{} ",JSON.toJSON(cookie));
        return cookie;
    }

    public AccessControlFilter getLoginAuthcFilter(MongoTemplate mongoTemplate) {
        AuthcAuthenticationFilter authcFilter = new AuthcAuthenticationFilter(mongoTemplate);
        authcFilter.setSessionIdCookie(getSessionIdCookie());
        authcFilter.setUsernameCookie(getUsernameCookie());
        authcFilter.setUserIdCookie(getUserIdCookie());
        authcFilter.setComIdCookie(getComIdCookie());
        authcFilter.setDomainCookie(getDomainCookie());
        authcFilter.setSuccessUrl("/");
        return authcFilter;
    }

    public AccessControlFilter getURLPermissionsFilter() {
        UriPermissionsFilter authorizationFilter = new UriPermissionsFilter();
        return authorizationFilter;
    }

    public AccessControlFilter getLogoutAuthenticationFilter() {
        LogoutAuthenticationFilter logoutAuthenticationFilter = new LogoutAuthenticationFilter();
        logoutAuthenticationFilter.setSessionIdCookie(getSessionIdCookie());
        logoutAuthenticationFilter.setUsernameCookie(getUsernameCookie());
        logoutAuthenticationFilter.setUserIdCookie(getUserIdCookie());
        logoutAuthenticationFilter.setComIdCookie(getComIdCookie());
        logoutAuthenticationFilter.setDomainCookie(getDomainCookie());
        return logoutAuthenticationFilter;
    }

    /**
     * 权限审核
     *
     * @return
     */
    public AccessControlFilter getAuthenticationFilter() {
//        AuthcAuthenticationFilter authcAuthenticationFilter=new AuthcAuthenticationFilter();
//TODO:
        AuthenticationFilter authenticationFilter = new AuthenticationFilter();
        authenticationFilter.setSessionIdCookie(getSessionIdCookie());
        authenticationFilter.setUsernameCookie(getUsernameCookie());
        authenticationFilter.setUserIdCookie(getUserIdCookie());
        authenticationFilter.setComIdCookie(getComIdCookie());
        authenticationFilter.setDomainCookie(getDomainCookie());
        authenticationFilter.setUpdateTtl(true);

        return authenticationFilter;
    }

    /**
     * 权限审核不需要需要更新ttl
     *
     * @return
     */
    public AccessControlFilter getNoUpdateTtlAuthenticationFilter() {
        AuthenticationFilter authenticationFilter = new AuthenticationFilter();
        authenticationFilter.setSessionIdCookie(getSessionIdCookie());
        authenticationFilter.setUsernameCookie(getUsernameCookie());
        authenticationFilter.setUserIdCookie(getUserIdCookie());
        authenticationFilter.setComIdCookie(getComIdCookie());
        authenticationFilter.setDomainCookie(getDomainCookie());
        authenticationFilter.setUpdateTtl(false);
        return authenticationFilter;
    }

    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(MongoTemplate mongoTemplate) {

        logger.debug("ShiroConfiguration shiroFilterFactoryBean init");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new AuthShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(getSecurityManager());

        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        shiroFilterFactoryBean.setSuccessUrl("/");

//        shiroFilterFactoryBean.getFilters().put("urlPerm", getURLPermissionsFilter());

        Map<String, String> filterChainDefinitionMap = Maps.newLinkedHashMap();
        filterChainDefinitionMap.put("/favicon.ico", "anon");

        // 匿名访问
        String[] anonPaths = StringUtils.split(StringUtils.trimToEmpty(authProperties.getAnonPaths()), ",");
        for (String anonPath : anonPaths) {
            logger.info("shiro add anon :{}", anonPath);
            filterChainDefinitionMap.put(anonPath, "anon");
        }

        shiroFilterFactoryBean.getFilters().put("authc_noTtl", getNoUpdateTtlAuthenticationFilter());

        if (authProperties.getLogin()) {
            logger.info(":>>> 加载登陆请求");
            shiroFilterFactoryBean.getFilters().put("authc", getLoginAuthcFilter(mongoTemplate));
            shiroFilterFactoryBean.getFilters().put("logout", getLogoutAuthenticationFilter());
            //TODO:
            filterChainDefinitionMap.put("/login", "authc");
            filterChainDefinitionMap.put("/user/info", "authc");

            filterChainDefinitionMap.put("/logout", "logout");
        } else {
            shiroFilterFactoryBean.getFilters().put("authc", getAuthenticationFilter());
        }
        // 登录校验且不更新TTL
        String[] noTtlPaths = StringUtils.split(StringUtils.trimToEmpty(authProperties.getNoTtlPaths()), ",");
        for (String noTtlPath : noTtlPaths) {
            logger.info(":>>> shiro add authc_noTtl :{}", noTtlPath);
            filterChainDefinitionMap.put(noTtlPath, "authc_noTtl");
        }

//        filterChainDefinitionMap.put("/**", "authc, urlPerm");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        logger.info(":>>> getShiroFilterFactoryBean filterChainDefinitionMap:{}", JSON.toJSONString(shiroFilterFactoryBean
                .getFilterChainDefinitionMap()));
        return shiroFilterFactoryBean;
    }
    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean() {
        MethodInvokingFactoryBean invokingFactoryBean = new MethodInvokingFactoryBean();
        invokingFactoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        DefaultWebSecurityManager[] arguments = {getSecurityManager()};
        invokingFactoryBean.setArguments(arguments);
        return invokingFactoryBean;
    }
}
